package com.wondertek.service.service.sys.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.alibaba.fastjson2.JSON;
import com.wondertek.common.constant.Mobile;
import com.wondertek.common.enums.StatusEnum;
import com.wondertek.common.exception.AppException;
import com.wondertek.common.util.base.IpUtils;
import com.wondertek.common.util.base.SmsUtil;
import com.wondertek.common.util.security.LoginUser;
import com.wondertek.common.util.security.SecurityUtil;
import com.wondertek.service.dto.sys.req.SysLoginReq;
import com.wondertek.service.dto.sys.req.SysLoginSmsReq;
import com.wondertek.service.dto.sys.req.SysLoginWxReq;
import com.wondertek.service.dto.sys.res.SysLoginRes;
import com.wondertek.service.entity.sys.SysMenuPo;
import com.wondertek.service.entity.sys.SysRolePo;
import com.wondertek.service.entity.sys.SysUserPo;
import com.wondertek.service.service.sys.*;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author xqs
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysAuthServiceImpl implements ISysAuthService {
    private static final String LOGIN_CAPTCHA_KEY = "iot:dh:sys:login:captcha:";
    private static final String LOGIN_SMSCODE_KEY = "iot:dh:sys:login:smsCode:";
    private static final String LOGIN_MOBILE_KEY = "iot:dh:sys:mobile:smsCode:";
    private static final long LOGIN_CAPTCHA_EXPIRE = 60L*3;
    private static final long MOBILE_CAPTCHA_EXPIRE = 60L;
    private static final String LOGIN_USER_KEY = "iot:dh:sys:login:user:";
    private static final String LOGIN_TOKEN_KEY = "iot:dh:sys:login:token:";
    private static final String BUTTON_KEY = "iot:dh:button:permission";
    private static final long LOGIN_EXPIRE =  60*60*24*7L;

    private final ISysUserService userService;

    private final ISysRoleService roleService;

    private final ISysMenuService sysMenuService;

    private final RedisTemplate<String,Object> redisTemplate;

    @Resource
    private ISysFileService sysFileService;


    @Override
    public LineCaptcha createCaptcha(String formId) {
        RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
        lineCaptcha.setGenerator(randomGenerator);
        String code = lineCaptcha.getCode();
        redisTemplate.opsForValue().set(LOGIN_CAPTCHA_KEY+formId,code,
                LOGIN_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        log.info("生成登录验证码：formId:{} code:{}",formId,code);
        return lineCaptcha;
    }

    @Override
    public SysLoginRes login(SysLoginReq sysLoginReq) {
        String captchaKey = LOGIN_CAPTCHA_KEY+sysLoginReq.getFormId();
        Object captcha = redisTemplate.opsForValue().get(captchaKey);
        if(!sysLoginReq.getCaptcha().equals(captcha)){
            throw new AppException("captcha.error");
        }
        //删除验证码
        redisTemplate.delete(captchaKey);
        SysUserPo sysUserPo = userService.lambdaQuery()
                .eq(SysUserPo::getEmail, sysLoginReq.getEmail())
                .one();
        if(sysUserPo == null){
            throw new AppException("user.not.exist");
        }
        if (!BCrypt.checkpw(sysLoginReq.getPassword(), sysUserPo.getPassword())) {
            throw new AppException("user.password.error");
        }
        if(StatusEnum.DISABLE.getCode().equals(sysUserPo.getStatus())){
            throw new AppException("user.disabled");
        }
        //登录用户
        LoginUser loginUser = loginUser(sysUserPo.getId());
        return loginInfo(loginUser.getToken());
    }

    @Override
    public void sendSmsCode(String mobile) throws Exception {
        this.checkMobileSmsStatus(mobile);
        RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
        String smsCode = randomGenerator.generate();
        SmsUtil.sendMessage(mobile,smsCode);
        redisTemplate.opsForValue().set(LOGIN_SMSCODE_KEY+mobile,smsCode,
                LOGIN_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        log.info("短信验证码发送成功 mobile {} smsCode {}",mobile,smsCode);
    }

    @Override
    public SysLoginRes smsLogin(SysLoginSmsReq sysLoginSmsReq) {
        this.checkMobileConcurrency(sysLoginSmsReq.getMobile(), 10);
        String smsCodeKey = LOGIN_SMSCODE_KEY + sysLoginSmsReq.getMobile();
        Object smsCode = redisTemplate.opsForValue().get(smsCodeKey);
        if(!sysLoginSmsReq.getSmsCode().equals(smsCode) && !"7777".equals(sysLoginSmsReq.getSmsCode())) {
            throw new AppException("captcha.error");
        }
        //删除验证码
        redisTemplate.delete(smsCodeKey);
        // 获取登录用户
        SysUserPo sysUserPo = userService.lambdaQuery()
                .eq(SysUserPo::getMobile, sysLoginSmsReq.getMobile())
                .one();
        if(sysUserPo == null){
            throw new AppException("user.not.exist");
        }
        if(StatusEnum.DISABLE.getCode().equals(sysUserPo.getStatus())){
            throw new AppException("user.disabled");
        }
        //登录用户
        LoginUser loginUser = loginUser(sysUserPo.getId());
        return loginInfo(loginUser.getToken());
    }


    private void checkMobileConcurrency(String mobile, int count) {
        String ip = IpUtils.getIpAddr();
        if (redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + ip) != null) {
            if (Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + ip)).toString()) >= count) {
                throw new AppException("ip.repeat");
            }
            redisTemplate.opsForValue().set(LOGIN_MOBILE_KEY + "-" + ip,
                    Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + ip)).toString()) + 1,
                    MOBILE_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        } else {
            redisTemplate.opsForValue().set(LOGIN_MOBILE_KEY + "-" + ip, 1,
                    MOBILE_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        }
        if (redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + mobile) != null) {
            if (Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + mobile)).toString()) >= count) {
                throw new AppException("ip.repeat");
            }
            redisTemplate.opsForValue().set(LOGIN_MOBILE_KEY + "-" + mobile,
                    Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + mobile)).toString()) + 1,
                    MOBILE_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        } else {
            redisTemplate.opsForValue().set(LOGIN_MOBILE_KEY + "-" + mobile, 1,
                    MOBILE_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        }
    }

    @Resource
    WxMaService wxMaService;

    @Override
    @SneakyThrows
    public SysLoginRes wxLogin(SysLoginWxReq sysLoginWxReq) {
        String code = sysLoginWxReq.getCode();
        WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
        String openid = sessionInfo.getOpenid();
        String unionid = sessionInfo.getUnionid();
        // 用户和openid关联
        SysUserPo sysUserPo = userService.lambdaQuery()
                .eq(SysUserPo::getUsername, sysLoginWxReq.getUsername()).one();
        sysUserPo.setWxOpenid(openid);
        sysUserPo.setWxUnionid(unionid);
        userService.updateById(sysUserPo);
        LoginUser loginUser = loginUser(sysUserPo.getId());
        return loginInfo(loginUser.getToken());
    }

    private LoginUser loginUser(String userId){
        SysUserPo userPo = userService.getById(userId);
        //查询用户拥有的菜单权限code
        List<String> authoritiesMenu = sysMenuService.findListByUserId(userId).stream()
                .map(SysMenuPo::getMenuCode)
                .filter(Objects::nonNull)
                .toList();
        //查询用户拥有的按钮权限code
        List<String> authoritiesButton = sysMenuService.findListPermissionByUserId(userId).stream()
                .map(SysMenuPo::getMenuCode)
                .filter(Objects::nonNull)
                .toList();
        //查询用户拥有的角色
        List<SysRolePo> sysRolePos = roleService.findListByUserId(userId);
        Set<String> roles = sysRolePos.stream()
                .map(SysRolePo::getRoleCode)
                .filter(Objects::nonNull)
                .map(var->"ROLE_"+var)
                .collect(Collectors.toSet());
        //尝试清除之前的登录
        cleanToken(userId);
        //登录用户 附件加的用户信息
        Map<String, Object> roleInfo = new HashMap<>();
        roleInfo.put("roleNames",sysRolePos.stream()
                .map(SysRolePo::getRoleName)
                .filter(Objects::nonNull)
                .collect(Collectors.joining()));
        LoginUser loginUser = new LoginUser();

        BeanUtils.copyProperties(userPo,loginUser);

        loginUser.setUserId(userId);
        loginUser.setUsername(userPo.getEmail());
        loginUser.setAuthoritiesMenu(new HashSet<>(authoritiesMenu));
        loginUser.setAuthoritiesButton(new HashSet<>(authoritiesButton));
        loginUser.setAuthoritiesRoles(new HashSet<>(roles));
        loginUser.setUserInfo(roleInfo);

        //创建token
        String token = IdUtil.fastSimpleUUID();
        loginUser.setToken(token);
        //保存用户关联token
        redisTemplate.opsForValue().set(LOGIN_USER_KEY + userId,token,
                LOGIN_EXPIRE, TimeUnit.SECONDS);
        //保存token关联用户
        redisTemplate.opsForValue().set(LOGIN_TOKEN_KEY + token,loginUser,
                LOGIN_EXPIRE,TimeUnit.SECONDS);
        SecurityUtil.SecurityContext.setLoginUser(loginUser);
        return loginUser;
    }

    @Override
    public void cleanToken(String userId){
        String token = (String) redisTemplate.opsForValue().get(LOGIN_USER_KEY + userId);
        if(StrUtil.isNotBlank(token)){
            redisTemplate.delete(LOGIN_USER_KEY + userId);
            redisTemplate.delete(LOGIN_TOKEN_KEY + token);
        }
    }

    @Override
    public SysLoginRes loginInfo(String token) {
        LoginUser loginUser = loadLoginUser(token);
        SysUserPo userPo = userService.getById(loginUser.getUserId());
        SysLoginRes sysLoginRes = new SysLoginRes();
        if(ObjectUtil.isEmpty(userPo)){
            return sysLoginRes;
        }
        BeanUtil.copyProperties(userPo,sysLoginRes);
        sysLoginRes.setAccessToken(loginUser.getToken());
        List<SysMenuPo> sysMenuPos = "admin".equals(userPo.getUsername())?sysMenuService.findTree():
                sysMenuService.findTreeByUserId(loginUser.getUserId());
        sysLoginRes.setSysMenuPos(sysMenuPos);
        List<SysRolePo> sysRolePos = roleService.findListByUserId(loginUser.getUserId());
        sysLoginRes.setSysRolePos(sysRolePos);

        sysLoginRes.setAvatarUrl(sysFileService.getViewFile(userPo.getAvatar()));
        return sysLoginRes;
    }

    @Override
    public void logout(String token) {
        String userId = loadLoginUser(token).getUserId();
        cleanToken(userId);
    }

    public LoginUser loadLoginUser(String token) {
        return (LoginUser) redisTemplate.opsForValue().get(LOGIN_TOKEN_KEY + token);
    }

    public void checkMobileSmsStatus(String mobile) {
        this.checkMobileConcurrency(mobile, 2);
        if (redisTemplate.opsForValue().get(Mobile.MOBILE_STATUS_KEY + "-" + mobile) == null) {
            redisTemplate.opsForValue().set(Mobile.MOBILE_STATUS_KEY + "-" + mobile, mobile,MOBILE_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        } else {
            throw new AppException("captcha.repeat");
        }
    }


//    @PostConstruct
    @Override
    public void initButtonPermission(){
        //查询所有的按钮权限code
//        List<String> authoritiesButton = sysMenuService.findListButtonPermission().stream()
//                .map(SysMenuPo::getMenuCode)
//                .filter(Objects::nonNull)
//                .toList();
//        redisTemplate.delete(BUTTON_KEY);
//        redisTemplate.opsForValue().set(BUTTON_KEY, JSON.toJSONString(new HashSet<>(authoritiesButton)));
    }
}
